WellCMS 1.1.02 任意用户密码重置漏洞
一、漏洞简介
二、漏洞影响
WellCMS 1.1.02
三、复现过程
漏洞分析
CMS中密码重置逻辑代码存放于 /route/user.php
中,在没有配置邮件服务情况加,我们可以在生成验证码后增加
message(0, '重置密码验证码为:'.$code);
代码弹出验证码,修改后需删除
/route/route_user.php
原缓存文件,重新执行弹出验证码代码便会生效,详细代码如下:
// 重设密码第 1 步 | reset password first step
if ($action == 'resetpw') {
// hook user_resetpw_get_post.php
!$conf['user_resetpw_on'] AND message(-1, '未开启密码找回功能!');
if ($method == 'GET') {
// hook user_resetpw_get_start.php
$header['title'] = lang('resetpw');
// hook user_resetpw_get_end.php
include _include(APP_PATH . 'view/htm/user_resetpw.htm');
} else if ($method == 'POST') {
// hook user_resetpw_post_start.php
$email = param('email');
empty($email) AND message('email', lang('please_input_email'));
!is_email($email, $err) AND message('email', $err);
$_user = user_read_by_email($email);
!$_user AND message('email', lang('email_is_not_in_use'));
$code = param('code');
empty($code) AND message('code', lang('please_input_verify_code'));
$sess_email = _SESSION('user_resetpw_email');
$sess_code = _SESSION('user_resetpw_code');
empty($sess_code) AND message('code', lang('click_to_get_verify_code'));
empty($sess_email) AND message('code', lang('click_to_get_verify_code'));
$email != $sess_email AND message('code', lang('verify_code_incorrect'));
$code != $sess_code AND message('code', lang('verify_code_incorrect'));
$_SESSION['resetpw_verify_ok'] = 1;
// hook user_resetpw_post_end.php
message(0, lang('check_ok_to_next_step'));
}
// 重设密码第 3 步 | reset password step 3
} elseif ($action == 'resetpw_complete') {
// hook user_resetpw_get_post.php
// 校验数据
$email = _SESSION('user_resetpw_email');
$resetpw_verify_ok = _SESSION('resetpw_verify_ok');
(empty($email) || empty($resetpw_verify_ok)) AND message(-1, lang('data_empty_to_last_step'));
$_user = user_read_by_email($email);
empty($_user) AND message(-1, lang('email_not_exists'));
$_uid = $_user['uid'];
if ($method == 'GET') {
// hook user_resetpw_get_start.php
$header['title'] = lang('resetpw');
// hook user_resetpw_get_end.php
include _include(APP_PATH . 'view/htm/user_resetpw_complete.htm');
} else if ($method == 'POST') {
// hook user_resetpw_post_start.php
$password = param('password');
empty($password) AND message('password', lang('please_input_password'));
$salt = $_user['salt'];
$password = md5($password . $salt);
!is_password($password, $err) AND message('password', $err);
user_update($_uid, array('password' => $password));
unset($_SESSION['user_resetpw_email']);
unset($_SESSION['user_resetpw_code']);
unset($_SESSION['resetpw_verify_ok']);
// hook user_resetpw_post_end.php
message(0, lang('modify_successfully'));
}
// 发送验证码
} elseif ($action == 'send_code') {
$method != 'POST' AND message(-1, lang('method_error'));
// hook user_sendcode_start.php
$action2 = param(2);
// 重置密码,往老地址发送
if ($action2 == 'user_resetpw') {
$email = param('email');
empty($email) AND message('email', lang('please_input_email'));
!is_email($email, $err) AND message('email', $err);
$_user = user_read_by_email($email);
empty($_user) AND message('email', lang('email_is_not_in_use'));
empty($conf['user_resetpw_on']) AND message(-1, lang('resetpw_not_on'));
$code = rand(100000, 999999);
$_SESSION['user_resetpw_email'] = $email;
$_SESSION['user_resetpw_code'] = $code;
message(0, '重置密码验证码为:'.$code);
}
}
梳理密码重置逻辑流程图如下(黑色实现箭头为漏洞利用的简单思路步骤):
从逻辑中可以看出,在第三部中,重置密码仅进行了简单的SESSION中存储数据是否为空的验证,而并未对密码重置用户邮箱进行严格验证;若重置密码时,SESSION中存储的邮箱非当前验证用户邮箱,便会造成任意密码重置漏洞;
漏洞复现
在此程序中,在获取验证码时刷新当前SESSION中存储的密码重置用户邮箱,而未对SESSION中resetpw_verify_ok数据进行清除,也因此造成了任意用户密码重置逻辑漏洞。
我们先使用自己的账户通过验证进入第三步的用户重设密码界面;在此时,SESSION中user_resetpw_email是当前通过验证的攻击账户邮箱,SESSION中resetpw_verify_ok值为1,浏览器界面如下图所示:
在第三步时,我们可以在浏览器中打开一个新的标签页,使用管理员的邮箱发送重置密码验证码请求,在同一浏览器中SESSION会话不会改变,此时
$_SESSION['user_resetpw_email'] = $email;
代码会将SESSION中存储的密码重置邮箱更新为管理员邮箱,如下图所示:
回到第一个标签页,刷新页面可以发现当前SESSION中存储的密码重置用户邮箱已经变为管理员用户邮箱(非必须刷新,密码重置第三部中邮箱取自SESSION,并非提交参数),我们提交重设密码请求后,即可更改管理员用户密码,如下图所示: